<template>
  <div>
    <div>{{ name }}</div>
    <!-- 
      组合式 API：
      setup() 函数：
      1、setup() 函数是 Vue3 特有的选项，作为组合式 API 的起点
      2、从组件的生命周期来看，他在 beforeCreate() 之前执行
      3、函数中 this 不是组件实例，是 undefined
      4、如果数据或者函数在模板中使用，需要在 setup 返回



      reactive() 函数
      通常使用它定义 对象类型 响应式数据

      疑问：以前在 data 函数中返回对象数据就是响应式的，现在 setup 中返回的对象数据是响应式的吗
        不是，需要使用 reactive 转成响应式
      使用步骤：
        1、从 vue 中导入 reactive 函数
        2、在 setup 函数中，使用 reactive 函数，传入一个普通对象，返回一个响应式数据对象
        3、最后 setup 函数返回一个对象，包含该响应式对象即可，模板中可使用
      语法：const 变量名 = reactive({a,b,c, ...})
      注意：reactive 函数不能直接赋值为空，如果要赋值为空，只能遍历清除值



      ref() 函数
      通常使用它定义响应式数据，不限类型

      使用步骤：
        1、从 vue 中导入 ref 函数
        2、在 setup 函数中，使用 ref 函数，传入普通函数（简单或者复杂），返回一个响应式数据
        3、最后 setup 函数返回一个对象，包含响应式数据即可
      注意：使用 ref 创建的数据，js 中需要 .value ， template 中可忽略
          即：如果在 template 中修改，和 vue2 相同方法，但是如果在 js 中修改，需要在变量名上加入 .value
      语法：const 变量名 = ref(任意类型默认数值)
    -->
    <h4>reactive 函数的使用：</h4>
    <div>{{ person }}</div>
    <button @click="changeName">点击修改</button>
    
    <h4>ref 函数的使用：</h4>
    <div>{{ username }}</div>
    <button @click="changeUsername">点击修改名字</button>
    <div>{{ refPerson }}</div>
    <button @click="changeRefPerson">点击修改</button>

    <!-- 



      computed 函数：
      步骤：
        1、从 vue 中导入 computed 函数
        2、在 setup 函数中，使用 computed 函数，传入一个函数，函数返回计算好的数据
        3、最后 setup 函数返回一个对象，包含该计算属性数据即可，然后模板内使用
      语法：const 变量 = computed(() => { return data })
    -->
    <h4>computed 的使用</h4>
    <div>原数组</div>
    <ul>
      <li v-for="item in list" :key="item">{{ item }}</li>
    </ul>
    <div>过滤后新数组</div>
    <ul>
      <li v-for="item in newList" :key="item">{{ item }}</li>
    </ul>
    <button @click="addNum">点击往原数组中添加数据</button>

    <!-- 




      watch 函数
      语法1（普通监听）：
        watch(变量, () => {})
      语法2（监听多个数据）：
        watch([变量名1, 变量名2, ...], () => {})
      语法3（深度监听）：
        watch(变量名, () => {

        }, {
          deep: true, // 开启深度监听
          immediate: true // 页面开启时执行一次
        })
      语法4（深度监听）（注意：此方法只监听对象上某个属性，不用单独加 deep）：
        watch(() => 对象.属性, () => {

        })
    -->
    <h4>watch 函数的使用</h4>
    <button @click="count++">点击变化：{{ count }}</button>
    <button @click="bool = !bool">点击变化：{{ bool }}</button>
    <div>{{ mafei }}</div>
    <button @click="changeMafei">点击修改上面马飞</button>

    <!-- 



      生命周期函数：
      使用步骤：
        1、先从vue中导入以on打头的生命周期钩子函数
        2、在setup函数中调用生命周期函数并传入回调函数
        3、生命周期钩子函数可以调用多次
      注意：
        1、在 vue3 中没有了 beforeCreate() 和 created() 周期了，直接写在 setup 里面即可
        2、vue3 的周期函数比 vue2 的周期函数先执行（即 onMounted 比 mounted 先执行）
      常用的为 onMounted() 可以在里面发起 ajax 请求，或者操作 dom
    -->

    <!-- 



      ref 的使用：
      1、ref 操作 dom
      步骤：
        1、创建 ref => const hRef = ref(null)
        2、模板中建立关联 => <h1 ref="hRef">我是标题</h1>
        3、使用 => hRef.value
      注意：定义的变量名要和标签的 ref 的值一样

      2、获取组件实例
      步骤：
        1、使用 <script setup> 的组件默认是关闭的，组件实例使用不到顶层的数据和函数
        2、需要配合 defineExpose 暴露给组件实例使用，暴露的响应式数据会自动解除响应式
      详情请看子组件
      注意：如若子组件导出暴露的方法或者变量，想改名的话，语法是 更改后变量名: 原来变量名 （千万别写反了）
      父组件想使用子组件的方法或者变量，只需： 子组件ref值.value.方法名/变量名 即可
    -->
    <h4 ref="refH4">ref 的使用（操作 dom 对象）</h4>
    <RefSon ref="refSon" />
    <button @click="getSonFn">点击调用子组件变量和方法</button>

    <!-- 



      父传子：
      步骤：
        1、父组件提供数据
        2、父组件将数据传给子组件（和 vue2 一模一样，通过自定义属性）
        3、子组件通过 defineProps 进行接收，注意需要加类型，否则会报错未定义
        4、子组件渲染父组件传递的数据
    -->
    <h4>这是测试父子通信的：</h4>
    <h4>父组件：</h4>
    <div>父组件数据（fatherMoney） ： {{ fatherMoney }}</div>
    <div>父组件数据（fatherString） ： {{ fatherString }}</div>
    <hr>
    <fatherSon
      :fatherMoney="fatherMoney"
      :fatherString="fatherString"
      @changeFatherMoney="changeFatherMoney"
      @changeFatherString="changeFatherString"
      @changeAll="changeAll"
    />

    <!-- 



      子传父：通过子传父修改值，因为单向数据流
      步骤：
        1、父组件准备自定义事件和回调函数，并传给子组件
        2、子组件中使用 defineEmits 声明自定义事件，获取 emit 方法，如：const emit = defineEmits(['父组件传过去方法名'])
        3、子组件使用方法：emit('父组件传过去方法名', 要更改的值)
    -->

    <!-- 



      依赖注入（跨组件通信）
      步骤：
        1、通过导入 provide 注入数据，使用：provide('数据名称', 数据)
        2、在孙子组件（或其他子组件）中接收注入的数据，导入 inject 并使用：const 变量 = inject('注入数据的名称')
      注意：通过依赖注入的数据，在孙子组件（或其他子组件）中，可以直接修改，但是不建议这么做，最好遵循单向数据流
      即重新定义一个方法，再 provide 出去，在子组件中接收后，使用该方法修改
    -->
    <h4>这是测试跨组件通信的（依赖注入）</h4>
    <son />
  </div>
</template>


<script setup>
  import { reactive, ref, computed, watch, onBeforeMount, onMounted, provide } from 'vue'
  // 之前 vue2 中导入之后，还需要注册，但是 vue3 中导入即注册
  // 测试通过 ref 获取子组件的方法和变量：
  import RefSon from './components/ref-son.vue'
  // 测试父子通信：
  import fatherSon from './components/father-son.vue'
  // 测试跨组件通信（依赖注入）
  import son from './components/son.vue';

  const name = 'hello'

  // reactive 函数使用：
  // 注意，reactive 函数不能直接赋值为空，如果要赋值为空，只能遍历清除值
  const person = reactive({
    name: 'hhh',
    age: 128
  })
  const changeName = () => {
    person.age++
  }

  // ref 函数的使用：
  const username = ref('马飞')
  const refPerson = ref({
    name: 'aaa',
    age: 18
  })
  // 注意：如果在 template 中修改，和 vue2 相同方法
  // 但是如果在 js 中修改，需要在变量名上加入 .value
  const changeUsername = () => {
    username.value = '修改后的马飞'
  }
  const changeRefPerson = () => {
    refPerson.value.age++
  }



  // computed 用法：
  const list = ref([1, 5, 7])
  const newList = computed(() => {
    return list.value.filter(item => item > 5)
  })
  // 点击往原数组添加数据：
  const addNum = () => {
    list.value.push(Math.random() * 10)
  }



  // watch 的用法：
  // 1、普通用法：
  let count = ref(0)
  watch(count, (newVal, oldVal) => {
    console.log('监听值count变化了，新值为：', newVal, '老值为：', oldVal)
  })

  // 2、监听两个数值：
  let bool = ref(false)
  // 传数组进去：
  watch([count, bool], (newVal, oldVal) => {
    console.log('监听两个值成功，新值为：', newVal, '老值为：', oldVal)
  })

  // 3、深度监听（和 vue2 一样，只需要一个 newVal 参数即可）：
  const mafei = ref({
    name: '马飞',
    age: 100
  })
  const changeMafei = () => {
    mafei.value.name = mafei.value.name + 1
    mafei.value.age++
  }
  watch(mafei, (newVal) => {
    console.log('深度监听成功，值变化了，新值为：', newVal)
  }, {
    deep: true, // 开启深度监听
    immediate: true // 页面加载后立即执行一次函数
  })



  // 生命周期函数：
  // mounted()
  onBeforeMount(() => {
    console.log('onBeforeMount 执行了')
  })
  onMounted(() => {
    console.log('onMounted 执行了')
  })



  // ref 的使用（操作 dom 对象）
  // 注意：定义的变量名要和标签的 ref 的值一样
  const refH4 = ref(null)
  // 注意：要使用 onMounted() 去拿到此标签
  onMounted(() => {
    // 通过 变量名.value 访问到该 dom，通过 变量名.value.innerHTML 访问到 dom 的页面数据
    console.log(refH4.value)
    // 可以在这里操作 dom
    refH4.value.style.color = 'orange'
  })

  // ref的使用（获取组件实例）
  const refSon = ref(null)
  onMounted(() => {
    console.log('组件挂载后，拿到子组件：', refSon.value)
  })
  // 子组件暴露的方法可以直接调用
  const getSonFn = () => {
    console.log(refSon.value.lalala)
    refSon.value.sonLog()
  }




  // 父子通信：
  // 父传过去是和之前 vue2 一模一样
  const fatherMoney = ref(1000)
  const fatherString = ref('呜啦啦啦')

  const changeFatherMoney = (num) => {
    fatherMoney.value = num
  }

  const changeFatherString = (str) => {
    fatherString.value = str
  }

  const changeAll = (num, str) => {
    fatherMoney.value = num
    fatherString.value = str
  }




  // 跨组件通信（依赖注入）
  // 父组件通过导入 provide 来注入数据：provide('数据名称', 数据)
  const money = ref(50)
  provide('toGrandSonMoney', money)

  // 建议使用遵循单项数据流的方式更改数据（注入方法）
  const changeThisMoney = (num) => {
    money.value = num
  }
  provide('changeThisMoney', changeThisMoney)
</script>

<style lang="scss" scoped></style>